/*
 *
 * Copyright(c) 2010 Safelayer Secure Communications
 *
 * This is open source software, placed under the terms of the
 * GNU GENERAL PUBLIC LICENSE Version 3.
 * Please see: http://www.gnu.org/licenses/gpl-3.0.txt
 * 
 * Permission to use, copy, modify, and distribute this software
 * for any purpose and without fee is hereby granted, provided
 * that the above copyright notices appear in all copies and that
 * both the copyright notice and this permission notice appear in
 * supporting documentation.
 * 
 */
package com.neuralnoise.foaf.core;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.ResourceUtils;

/**
 * Merge engine to perfom smushing opertaions with FOAFs.
 * 
 * @author victor.martinez <swtp@safelayer.com>
 * 
 */
public class MergeEngine {

	public static String TEMPORAL_PERSON_URI = "http://temoral/id/primaryPerson";

	/**
	 * Attempts to find the primary person for each FOAF RDF graph.
	 * 
	 * @param models FOAF memory models to find primary people.
	 * @return The primary person found.
	 */
	public static List<Resource> getPrimaryPeople(List<Model> models) {
		List<Resource> primaryPeopleSelected = new ArrayList<Resource>();
		for (Model model : models) {
			Resource primaryPerson = SearchUtils.searchPrimaryPerson(model, "");
			if (primaryPerson != null) {
				primaryPeopleSelected.add(primaryPerson);
			}
		}
		return primaryPeopleSelected;
	}

	/**
	 * Performs the union of the given primary people. An union is a sets of statements which
	 * represent each given resource.
	 * 
	 * @param resources The resources to be merged.
	 * @return The result model merged.
	 */
	public static Model mergeResources(List<Resource> resources) {
		Model result = ModelFactory.createDefaultModel();
		for (Resource resource : resources) {
			Resource resourceToMerge = copyResource(resource);
			resourceToMerge = renameTmpResource(resource);
			result = mergeModels(resourceToMerge.getModel(), result);
		}
		return result;
	}

	/**
	 * Returns the accumulation of FOAF contacts included for each input model.
	 * 
	 * @return the accumulation of FOAF contacts included for each input model.
	 */
	public static List<WrappedContact> getAllContacts(List<Model> models) {
		List<WrappedContact> contacts = new ArrayList<WrappedContact>();
		for (Model model : models) {
			// retrieve contacts
			NodeIterator contactsIter = model.listObjectsOfProperty(FOAFVocabulary.knows);
			int modelIndex = 0;
			while (contactsIter.hasNext()) {
				RDFNode node = contactsIter.next();
				if (!node.isResource())
					continue;
				Resource resource = (Resource) node; // safe cast
				// create WrappedContact
				WrappedContact contact = new WrappedContact(resource, modelIndex);
				contacts.add(contact);
				modelIndex++;
			}
		}
		return contacts;
	}

	/**
	 * Main method to merge a list of {@link WrappedContact}
	 * 
	 * @param contactsToMerge The list of WrappedContacts to merge.
	 * @param trustSmushing if <tt>true</tt> apply only trust smushing rules else apply both(trust
	 *            and untrust)
	 * @return
	 */
	public static List<Resource> mergeContacts(List<WrappedContact> contactsToMerge, boolean trustSmushing) {
		List<WrappedContact> contactsMerged = mergeWrappedContacts(contactsToMerge, trustSmushing);
		return unwrapContactsResources(contactsMerged);
	}

	/**
	 * Adds the given contacts to the given primary person model.
	 * 
	 * @param model The primary person to add the contacts.
	 * @param contact The contacts to add.
	 * @return the model with the contacts added
	 */
	public static Model addContacts(Model model, List<Resource> contactsMerged) {
		Resource primaryPerson = SearchUtils.searchPrimaryPerson(model, "");
		for (Resource resource : contactsMerged) {
			primaryPerson.addProperty(FOAFVocabulary.knows, resource);
		}
		return model;
	}

	/**
	 * Attempt to rename the primary person from the given model with the given uri.
	 * 
	 * @param model The model where the primary person is.
	 * @param sourceUri The primary person uri.
	 * @param destUri The uri value destination.
	 * @return The model where the primary person is to be renamed.
	 */
	public static Model renamePrimaryPerson(Model model, String sourceUri, String destUri) {
		Resource primaryPerson = SearchUtils.searchPrimaryPerson(model, sourceUri);
		ResourceUtils.renameResource(primaryPerson, destUri);
		return model;
	}

	// Helpers -----------------------------------------------------------------------------------

	private static List<WrappedContact> mergeWrappedContacts(List<WrappedContact> contactsToMerge, boolean trustSmushing) {
		List<WrappedContact> contactsToMergeCopy = new ArrayList<WrappedContact>(contactsToMerge);
		List<WrappedContact> contactsMerged = new ArrayList<WrappedContact>();
		boolean outterContactMerged;
		Iterator<WrappedContact> outterIterator = contactsToMerge.iterator();
		while (outterIterator.hasNext()) {
			WrappedContact outterContact = outterIterator.next();
			outterContactMerged = false;
			Iterator<WrappedContact> innerIterator = contactsToMergeCopy.iterator();
			while (innerIterator.hasNext()) {
				WrappedContact innerContact = innerIterator.next();
				if (outterContact.equalsContacts(innerContact, trustSmushing)) {
					outterContact = outterContact.merge(innerContact);
					outterContactMerged = true;
					innerIterator.remove();
				}
			}
			if (outterContactMerged)
				contactsMerged.add(outterContact);
		}
		return contactsMerged;
	}

	private static List<Resource> unwrapContactsResources(List<WrappedContact> contactsMerged) {
		List<Resource> results = new ArrayList<Resource>();
		for (WrappedContact contact : contactsMerged) {
			results.add(contact.getResult());
		}
		return results;
	}

	private static Resource copyResource(Resource resource) {
		Model newModel = ModelFactory.createDefaultModel();
		return newModel.createResource(resource);
	}

	private static Resource renameTmpResource(Resource resource) {
		return ResourceUtils.renameResource(resource, TEMPORAL_PERSON_URI);
	}

	private static Model mergeModels(Model model1, Model model2) {
		Model mergeModel = model1.union(model2);
		return mergeModel;
	}
}
